<div id="app"></div>
<style>
    #app {
        text-align: center;
        font-size: 60px;
    }
</style>
<script>
    /**
     * h 方法返回一个扁平的 虚拟dom对象
     * @params 标签名
     * @props 标签属性
     * @children 子标签
     *
     */
    const h = (tag, props, children) => {
        console.log('%c--h func', 'color: red;')
        return {
            tag,
            props,
            children
        }
    }

    /**
     * mount 函数接受两个参数,第一个是虚拟节点也就是 h 函数生成的 vnode, 第二个是要挂载到的真实 dom 节点. mount 的主要作用是将虚拟节点挂载到真实 dom 节点上
     * @param vnode 虚拟dom
     * @param container 挂载容器
     */
    function mount(vnode, container) {
        console.log('%c--mount func', 'color: red;')
        const el = vnode.el = document.createElement(vnode.tag)
        // 处理props
        for (const key in vnode.props) {
            const value = vnode.props[key]
            if (key.startsWith('on')) {
                el.addEventListener(key.slice(2).toLocaleLowerCase(), value)
            } else {
                el.setAttribute(key, value)
            }

        }
        // 处理children
        if (vnode.children) {
            if (Array.isArray(vnode.children)) {
                vnode.children.forEach((child) => {
                    if (typeof child === "string") {
                        el.append(child);
                    } else if (typeof child === "object") {
                        //child是一个对象时,说明child是一个元素节点
                        //这时就要递归调用mount
                        mount(child, el);
                    }
                });
            } else {
                console.log('mount', vnode.children)
                el.append(vnode.children);
            }
        }

        container.appendChild(el)
    }

    /**
     * 比较新老vdom对象 进行最少的dom操作
     * @param n1 旧的vdom
     * @param n2 新的vdom
     */
    const patch = (n1, n2) => {
        console.log('%c--patch func', 'color: red;')
        console.log(n1, n2)
        // 标签名相同
        if (n1.tag === n2.tag) {
            let el = n2.el = n1.el;
            const oldProps = n1.props || {}
            const newProps = n2.props || {}
            // 循环新的props 新旧值比较
            for (const key in newProps) {
                const oldValue = oldProps[key]
                const newValue = newProps[key]
                if (newValue !== oldValue) {
                    el.setAttribute(key, newValue)
                }
            }
            // 循环老的props 删除不存在的值
            for (const key in oldProps) {
                if (!(key in newProps)) {
                    el.removeAttribute(key)
                }
            }


            const oldChildren = n1.children
            const newChildren = n2.children
            if (typeof newChildren === 'string') {
                if (typeof oldChildren === 'string') {
                    if (oldChildren !== newChildren) {
                        el.textContent = newChildren;
                    }
                } else {
                    el.textContent = newChildren;
                }
            } else {
                if (typeof oldChildren === 'string') {
                    el.innerHTML = ''
                    newChildren.forEach(item => {
                        mount(item, el)
                    })
                } else {
                    const commonLength = Math.min(oldChildren.length, newChildren.length)
                    for (let i = 0; i < commonLength; i++) {
                        patch(oldChildren[i], newChildren[i])
                    }
                    if (newChildren.length > oldChildren.length) {
                        newChildren.splice(oldChildren.length).forEach(child => {
                            mount(child, el)
                        })
                    } else if (newChildren.length < oldChildren.length) {
                        oldChildren.splice(newChildren.length).forEach(child => {
                            el.removeChild(child.el)
                        })
                    }
                }
            }
        } else {
            // todo 标签不一样
        }
    }

    // reactive

    // 存放出发依赖的func
    // watchEffect 传递的func
    let activeEffect

    class Dep {
        subscribers = new Set()

        // 依赖收集
        depend() {
            if (activeEffect) {
                console.log('depend:', activeEffect)
                this.subscribers.add(activeEffect)
            }
        }

        // 触发依赖
        notify() {
            console.log('notify:', this.subscribers)
            this.subscribers.forEach(effect => {
                effect()
            })
        }
    }

    function watchEffect(effect) {
        activeEffect = effect
        effect()
        activeEffect = null
    }

    const targetsMap = new WeakMap()

    /**
     *  为每个响应式对象创建一个map 存放dep实例 有则返回 无则创建
     *
     * @param target 响应对象
     * @param key
     * @returns dep 对象
     */
    function getDep(target, key) {
        let depsMap = targetsMap.get(target)
        if (!depsMap) {
            depsMap = new Map()
            targetsMap.set(target, depsMap)
        }
        let dep = depsMap.get(key)
        if (!dep) {
            dep = new Dep()
            depsMap.set(key, dep)
        }
        return dep
    }

    let reactiveHandlers = {
        get(target, key, receiver) {
            let dep = getDep(target, key)
            dep.depend()
            return Reflect.get(target, key, receiver)
        },
        set(target, key, value, receiver) {
            let dep = getDep(target, key)
            let result = Reflect.set(target, key, value, receiver)
            dep.notify()
            return result
        }
    }

    function reactive(raw) {
        console.log('%c--reactive func', 'color: red;')
        return new Proxy(raw, reactiveHandlers)
    }


    const App = {
        data: reactive({
            count: 0
        }),
        render() {
            console.log('%c--render func', 'color: red;')
            return h('button', {
                onClick: () => {
                    this.data.count++
                }
            }, `count: ${this.data.count}`)
        }
    }

    function mountApp(component, container) {
        console.log('%c--mountApp func', 'color: red;')
        let isMounted = false
        let preVdom
        watchEffect(() => {
            console.log('watchEffect')
            if (!isMounted) {
                // render时 触发 Proxy get 进行依赖收集
                preVdom = component.render()
                mount(preVdom, container)
                isMounted = true
            } else {
                const newVdom = component.render()
                patch(preVdom, newVdom)
                preVdom = newVdom
            }
        })
    }

    function createApp () {
        mountApp(App, document.getElementById('app'))
    }
    createApp()
</script>